package org.swing;

import com.jgoodies.validation.*;
import com.jgoodies.validation.view.ValidationComponentUtils;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import javax.swing.text.NumberFormatter;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Vector;

public class JExtNumberField extends JComponent /*implements Validatable*/ {
    private final JExtNumberTextEditor editor = new JExtNumberTextEditor();
    private final RendererTextField renderer = new RendererTextField();

    private boolean groupingUsed;
    private EventListenerList listenerList = new EventListenerList();
    private NumberFormatter numberFormatter;

    public JExtNumberField() {
        final CardLayout cardLayout = new CardLayout();
        setLayout(cardLayout);
        renderer.setRequestFocusEnabled(true);
        renderer.setFocusable(true);
        renderer.setHorizontalAlignment(JLabel.RIGHT);
        setGroupingUsed(true);

        add(renderer, "renderer");
        add(editor, "editor");
        renderer.addFocusListener(new FocusAdapter() {
            /**
             * Invoked when a component gains the keyboard focus.
             */
            @Override
            public void focusGained(FocusEvent e) {
                setFocusable(true);
                requestFocus();
                showEditor(cardLayout);
                editor.requestFocus();
                setFocusable(false);
                e.setSource(JExtNumberField.this);
                fireFocusEvent(e, true);
            }
        });
        renderer.addMouseListener(new MouseAdapter() {
            /**
             * {@inheritDoc}
             */
            @Override
            public void mouseEntered(MouseEvent e) {
                showEditor(cardLayout);
            }
        });
        editor.addFocusListener(new FocusAdapter() {
            /**
             * Invoked when a component loses the keyboard focus.
             */
            @Override
            public void focusLost(FocusEvent e) {
                if (!(e.isTemporary() || e.getOppositeComponent() instanceof JBusyPanel)) {
                    showRenderer(cardLayout);
                }
                e.setSource(JExtNumberField.this);
                fireFocusEvent(e, false);
            }
        });
        editor.addMouseListener(new MouseAdapter() {
            /**
             * {@inheritDoc}
             */
            @Override
            public void mouseExited(MouseEvent e) {
                if (!editor.hasFocus()) showRenderer(cardLayout);
            }
        });

        editor.addPropertyChangeListener("value", new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                try {
                    renderer.setText(numberFormatter.valueToString(editor.getValue()));
                } catch (Exception e) {
                    renderer.setText(editor.getText());
                }

            }
        });
    }

    public void setGroupingUsed(boolean groupingUsed) {
        this.groupingUsed = groupingUsed;
        NumberFormat numberInstance = NumberFormat.getNumberInstance();
        numberInstance.setGroupingUsed(groupingUsed);

        numberFormatter = new NumberFormatter(numberInstance);
    }

    public boolean isGroupingUsed() {
        return groupingUsed;
    }

    private void showRenderer(CardLayout cardLayout) {
        if (this.getLayout() != null) cardLayout.show(this, "renderer");
    }

    private void showEditor(CardLayout cardLayout) {
        if (this.getLayout() != null) cardLayout.show(this, "editor");
    }

    /**
     * If the <code>preferredSize</code> has been set to a
     * non-<code>null</code> value just returns it.
     * If the UI delegate's <code>getPreferredSize</code>
     * method returns a non <code>null</code> value then return that;
     * otherwise defer to the component's layout manager.
     *
     * @return the value of the <code>preferredSize</code> property
     * @see #setPreferredSize
     * @see javax.swing.plaf.ComponentUI
     */
    @Override
    public Dimension getPreferredSize() {
        return editor.getPreferredSize();
    }

    public void setAllowNegative(boolean allowNegative) {
        editor.setAllowNegative(allowNegative);

    }

    public void setFormat(JExtNumberTextEditor.NumberType numberType) {
        editor.setFormat(numberType);
    }

    public void setColumns(int col) {
        editor.setColumns(col);
        renderer.setColumns(col);
    }

    public void setMaxLength(int maxLen) {
        editor.setMaxLength(maxLen);
    }

    public int getMaxLength() {
        return editor.getMaxLength();
    }

    public void setPrecision(int iPrecision) {
        editor.setPrecision(iPrecision);
    }

    public int getPrecision() {
        return editor.getPrecision();
    }

    public void setNumber(Integer value) {
        editor.setValue(value);

    }

    public void setNumber(Short value) {
        editor.setValue(value);

    }

    public void setNumber(BigDecimal value) {
        editor.setValue(value);

    }

    public Integer getInteger() {
        return editor.getInteger();
    }

    public BigDecimal getBigDecimal() {
        return editor.getValue();
    }

    public JExtNumberTextEditor.NumberType getFormat() {
        return editor.getFormat();
    }

    public boolean isAllowNegative() {
        return editor.isAllowNegative();
    }

    /**
     * Gets the font of this component.
     *
     * @return this component's font; if a font has not been set
     *         for this component, the font of its parent is returned
     * @see #setFont
     * @since JDK1.0
     */
    @Override
    public Font getFont() {
        return editor.getFont();
    }

    /**
     * Sets the font for this component.
     *
     * @param font the desired <code>Font</code> for this component
     * @beaninfo preferred: true
     * bound: true
     * attribute: visualUpdate true
     * description: The font for the component.
     * @see java.awt.Component#getFont
     */
    @Override
    public void setFont(Font font) {
        editor.setFont(font);
        renderer.setFont(font);
    }

    /**
     * Adds the specified focus listener to receive focus events from
     * this component when this component gains input focus.
     * If listener <code>l</code> is <code>null</code>,
     * no exception is thrown and no action is performed.
     * <p>Refer to <a href="doc-files/AWTThreadIssues.html#ListenersThreads"
     * >AWT Threading Issues</a> for details on AWT's threading model.
     *
     * @param l the focus listener
     * @see java.awt.event.FocusEvent
     * @see java.awt.event.FocusListener
     * @see #removeFocusListener
     * @see #getFocusListeners
     * @since JDK1.1
     */
    @Override
    public synchronized void addFocusListener(FocusListener l) {
        listenerList.add(FocusListener.class, l);
    }

    /**
     * Removes the specified focus listener so that it no longer
     * receives focus events from this component. This method performs
     * no function, nor does it throw an exception, if the listener
     * specified by the argument was not previously added to this component.
     * If listener <code>l</code> is <code>null</code>,
     * no exception is thrown and no action is performed.
     * <p>Refer to <a href="doc-files/AWTThreadIssues.html#ListenersThreads"
     * >AWT Threading Issues</a> for details on AWT's threading model.
     *
     * @param l the focus listener
     * @see java.awt.event.FocusEvent
     * @see java.awt.event.FocusListener
     * @see #addFocusListener
     * @see #getFocusListeners
     * @since JDK1.1
     */
    @Override
    public synchronized void removeFocusListener(FocusListener l) {
        listenerList.remove(FocusListener.class, l);
    }

    public boolean isEditable() {
        return editor.isEditable();
    }

    public void setEditable(boolean editable) {
        editor.setEditable(editable);
        renderer.setEditable(editable);
    }

    /**
     * Sets whether or not this component is enabled.
     * A component that is enabled may respond to user input,
     * while a component that is not enabled cannot respond to
     * user input.  Some components may alter their visual
     * representation when they are disabled in order to
     * provide feedback to the user that they cannot take input.
     * <p>Note: Disabling a component does not disable its children.
     * <p/>
     * <p>Note: Disabling a lightweight component does not prevent it from
     * receiving MouseEvents.
     *
     * @param enabled true if this component should be enabled, false otherwise
     * @beaninfo preferred: true
     * bound: true
     * attribute: visualUpdate true
     * description: The enabled state of the component.
     * @see java.awt.Component#isEnabled
     * @see java.awt.Component#isLightweight
     */
    @Override
    public void setEnabled(boolean enabled) {
        editor.setEnabled(enabled);
        renderer.setEnabled(enabled);
    }

    /**
     * Determines whether this component is enabled. An enabled component
     * can respond to user input and generate events. Components are
     * enabled initially by default. A component may be enabled or disabled by
     * calling its <code>setEnabled</code> method.
     *
     * @return <code>true</code> if the component is enabled,
     *         <code>false</code> otherwise
     * @see #setEnabled
     * @since JDK1.0
     */
    @Override
    public boolean isEnabled() {
        return editor.isEnabled();
    }

    private void fireFocusEvent(FocusEvent event, boolean gained) {
        FocusListener[] listeners = listenerList.getListeners(FocusListener.class);
        for (FocusListener listener : listeners) {
            if (gained) listener.focusGained(event);
            else listener.focusLost(event);
        }
    }

    public void addValidater(IconFeedbackPanel iconFeedbackPanel, Validator validator, ValidationType validationType) {
        editor.addValidater(iconFeedbackPanel, validator, validationType);
        renderer.addValidater(iconFeedbackPanel, validator, validationType);
    }

    public void removeValidater() {
        editor.removeValidater();
    }

    /**
     * Validates this object and returns a {@link com.jgoodies.validation.ValidationResult}.
     *
     * @return the validation result
     */
    public Vector<ValidationMessage> validateValue() {
        return editor.validateValue();
    }

    public int getSerialValue() {
        return editor.getSerialValue();
    }

    public void setMandarory(boolean mandatory) {
        editor.setMandarory(mandatory);
        renderer.setMandarory(mandatory);
    }

    public boolean isMandatory() {
        return editor.isMandatory();
    }

    private class RendererTextField extends JTextField implements Validatable {

        /**
         * Constructs a new <code>TextField</code>.  A default model is created,
         * the initial string is <code>null</code>,
         * and the number of columns is set to 0.
         */
        private RendererTextField() {
            ValidationComponentUtils.setMessageKey(this, editor.getSerialValue());
        }

        /**
         * Selects all the text in the <code>TextComponent</code>.
         * Does nothing on a <code>null</code> or empty document.
         */
        @Override
        public void selectAll() {
        }

        public void addValidater(IconFeedbackPanel iconFeedbackPanel, Validator validator, ValidationType validationType) {
            iconFeedbackPanel.registerComponent(this);
        }

        public void removeValidater() {

        }

        /**
         * Validates this object and returns a {@link com.jgoodies.validation.ValidationResult}.
         *
         * @return the validation result
         */
        public Vector<ValidationMessage> validateValue() {
            return new Vector<ValidationMessage>();
        }

        public int getSerialValue() {
            return editor.getSerialValue();
        }

        public void setMandarory(boolean mandatory) {
            ValidationComponentUtils.setMandatory(this, mandatory);
        }

        public boolean isMandatory() {
            return ValidationComponentUtils.isMandatory(this);
        }
    }
}